package org.lex.jdbc;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyDriverManager {
	public static Connection getConnection(String url) throws SQLException {
		return (getConnection(url, new Properties()));
	}

	public static Connection getConnection(String url, String user,
			String password) throws SQLException {
		Properties info = new Properties();
		if (user != null) {
			info.put("user", user);
		}
		if (password != null) {
			info.put("password", password);
		}
		return (getConnection(url, info));
	}

	public static Connection getConnection(String url, Properties info)
			throws SQLException {
		Vector<DriverInfo> drivers = null;
		if (url == null) {
			throw new SQLException("The url cannot be null", "08001");
		}

		log.debug("DriverManager.getConnection(url=\"{}\")", url);

		synchronized (MyDriverManager.class) {
			// use the readcopy of drivers
			drivers = readDrivers;
		}

		// Walk through the loaded drivers attempting to make a connection.
		// Remember the first exception that gets raised so we can reraise it.
		SQLException reason = null;
		for (int i = 0; i < drivers.size(); i++) {
			DriverInfo di = drivers.elementAt(i);
			try {
				log.debug("    trying {} ...", di);
				Connection result = di.driver.connect(url, info);
				if (result != null) {
					log.debug("getConnection returns {}", di);
					return (result);
				}
			} catch (SQLException ex) {
				if (reason == null) {
					reason = ex;
				}
			}
		}

		Connection result = DriverManager.getConnection(url, info);
		if (null != result) {
			log.debug("DriverManager getConnection success");
			return (result);
		}

		// if we got here nobody could connect.
		if (reason != null) {
			log.debug("getConnection failed: {}", reason);
			throw reason;
		}

		log.debug("getConnection: no suitable driver found for url: {}", url);
		throw new SQLException("No suitable driver found for " + url, "08001");
	}

	public static Driver getDriver(String url) throws SQLException {
		Vector<DriverInfo> drivers = null;
		log.debug("DriverManager.getDriver(\"url={}\")", url);

		synchronized (MyDriverManager.class) {
			// use the read copy of the drivers vector
			drivers = readDrivers;
		}

		// Walk through the loaded drivers attempting to locate someone
		// who understands the given URL.
		for (int i = 0; i < drivers.size(); i++) {
			DriverInfo di = drivers.elementAt(i);
			try {
				log.debug(("    trying " + di));
				if (di.driver.acceptsURL(url)) {
					// Success!
					log.debug("getDriver returns {}", di);
					return (di.driver);
				}
			} catch (SQLException ex) {
				// Drop through and try the next driver.
			}
		}

		Driver driver = DriverManager.getDriver(url);
		if (null != driver) {
			log.debug("DriverManager getDriver success");
			return driver;
		}

		log.debug("getDriver: no suitable driver");
		throw new SQLException("No suitable driver", "08001");
	}

	/**
	 * Registers the given driver with the <code>DriverManager</code>. A
	 * newly-loaded driver class should call the method
	 * <code>registerDriver</code> to make itself known to the
	 * <code>DriverManager</code>.
	 * 
	 * @param driver
	 *            the new JDBC Driver that is to be registered with the
	 *            <code>DriverManager</code>
	 * @exception SQLException
	 *                if a database access error occurs
	 */
	@SuppressWarnings("unchecked")
	public static synchronized void registerDriver(java.sql.Driver driver)
			throws SQLException {
		DriverInfo di = new DriverInfo();
		di.driver = driver;
		di.driverClass = driver.getClass();
		di.driverClassName = di.driverClass.getName();

		writeDrivers.addElement(di);
		log.debug(("registerDriver: " + di));

		/* update the read copy of drivers vector */
		readDrivers = (Vector<DriverInfo>) writeDrivers.clone();

		DriverManager.registerDriver(driver);
	}

	/**
	 * Drops a driver from the <code>DriverManager</code>'s list. Applets can
	 * only deregister drivers from their own classloaders.
	 * 
	 * @param driver
	 *            the JDBC Driver to drop
	 * @exception SQLException
	 *                if a database access error occurs
	 */
	@SuppressWarnings("unchecked")
	public static synchronized void deregisterDriver(Driver driver)
			throws SQLException {
		log.debug(("DriverManager.deregisterDriver: " + driver));

		// Walk through the loaded drivers.
		int i;
		DriverInfo di = null;
		for (i = 0; i < writeDrivers.size(); i++) {
			di = writeDrivers.elementAt(i);
			if (di.driver == driver) {
				break;
			}
		}
		// If we can't find the driver just return.
		if (i >= writeDrivers.size()) {
			log.debug("    couldn't find driver to unload");
			return;
		}

		// Remove the driver. Other entries in drivers get shuffled down.
		writeDrivers.removeElementAt(i);

		/* update the read copy of drivers vector */
		readDrivers = (Vector<DriverInfo>) writeDrivers.clone();

		DriverManager.deregisterDriver(driver);
	}

	/**
	 * Retrieves an Enumeration with all of the currently loaded JDBC drivers to
	 * which the current caller has access.
	 * 
	 * <P>
	 * <B>Note:</B> The classname of a driver can be found using
	 * <CODE>d.getClass().getName()</CODE>
	 * 
	 * @return the list of JDBC Drivers loaded by the caller's class loader
	 */
	public static Enumeration<Driver> getDrivers() {
		Vector<Driver> result = new Vector<Driver>();
		Vector<DriverInfo> drivers = null;

		synchronized (MyDriverManager.class) {
			// use the readcopy of drivers
			drivers = readDrivers;
		}

		// Walk through the loaded drivers.
		for (int i = 0; i < drivers.size(); i++) {
			DriverInfo di = drivers.elementAt(i);
			result.addElement(di.driver);
		}

		return (result.elements());
	}

	/* Prevent the DriverManager class from being instantiated. */
	private MyDriverManager() {
	}

	/* write copy of the drivers vector */
	private static Vector<DriverInfo> writeDrivers = new Vector<DriverInfo>();

	/* write copy of the drivers vector */
	private static Vector<DriverInfo> readDrivers = new Vector<DriverInfo>();

	private static Logger log = LoggerFactory.getLogger(MyDriverManager.class);

}

// DriverInfo is a package-private support class.
class DriverInfo {
	Driver driver;
	Class<?> driverClass;
	String driverClassName;

	@Override
	public String toString() {
		return ("driver[className=" + driverClassName + "," + driver + "]");
	}
}
